
// 
export class Component {
  constructor() {
    this.children = [];
    // 实现 propos 属性，创建一个空函数，使用 null 是不会带有 object 的原型函数
    this.props = Object.create(null);
    this.range;
    this.state;
  }
  setAttribute(key, value) {
    // 使用继承 Component 需要处理有 root 节点的元素信息为其设置属性
    // if (this.root) {
    //   this.root.setAttribute(key, value);
    // }

    
    // 为 this.props 添加属性
    this.props[key] = value;
    // this[key] = value;
  }
  appendChild(vchild) {
    this.children.push(vchild);
    // vchild.mountTo(this.root);
  }
  // 装载函数，将 render 后的元素装载到指定元素中
  // 使用了 document.createRange 函数的实例
  mountTo(elemRange) {
    this.range = elemRange;
    this.update(); // 由 update 来实现更新 vdom
  }
  // 
  update () {
    // range 存储是真实 dom，然后就需要进行 vdom 和 dom 进行比对就依赖 range
    // this.range.deleteContents(); // 将 range 清空
    // const vdom = this.render(); // 重新渲染 vdom
    // vdom.mountTo(this.range); // vdom 渲染成真实 dom

    const tphd = document.createComment("placeholder"); // 创建一个注释节点占位
    const tempRange = document.createRange(); // 创建 range 对象
    tempRange.setStart(this.range.endContainer, this.range.endOffset); // range 对象的开始位置
    tempRange.setEnd(this.range.endContainer, this.range.endOffset); // range 对象的结束位置
    tempRange.insertNode(tphd); // 插入占位节点

    this.range.deleteContents(); // 将 range 清空

    const vdom = this.render(); // 重新渲染 vdom
    vdom.mountTo(this.range); // vdom 渲染成真实 dom

  }
  // 状态函数的实现，相当于 merge 合并，但是合并完成后需要 reRender
  setState(state) {
    // 合并函数，第二节 39:00
    const merge = (oldState, newState) => {
      // 遍历新的状态函数
      for (const key in newState) {
        if (typeof newState[key] === "object" && newState[key] !== null) {
          if (typeof oldState[key] !== "object") {
            // 如果新状态属性是一个对象，而旧状态属性不是一个对象
            // 处理对象和数组的不同情况赋予类型不同
            Array.isArray(newState[key]) ? oldState[key] = [] : oldState[key] = {};
          }
          // 递归调用
          merge(oldState[key], newState[key]);
        } else {
          oldState[key] = newState[key];
        }
      }
    };
    // 如果状态对象不存在则默认空对象
    if (!this.state && state) {
      this.state = {};
    }
    // 合并状态对象
    merge(this.state, state);
    // 最后调用更新元素
    this.update();
  }

  get vdom () {
    return this.render().vdom;
  }
}
// 元素封装类实现
// extends Component 暂时不实现 继承方式
class ElementWrapper {
  // 使用继承的方式
  constructor(type) {
    // super(); // 继承需要调用执行父类的构造函数
    // 要实现 vdom 就不应该在 ElementWrapper 实例化的时候创建
    // this.root = document.createElement(content);
    this.type = type;
    this.props = Object.create(null);
    this.children = [];
  }
  // 使用继承后不用再次实现函数
  setAttribute(key, value) {
    this.props[key] = value;
    // 可以直接调用父类的函数
    // super.setAttribute(key, value);
  }
  // 添加子元素函数
  appendChild(vchild) {
    this.children.push(vchild);
  }
  // 装载函数，将创建后的元素装载到指定元素中
  // 实体 dom 应该在这里实现
  mountTo(elemRange) {
    // 先清空 range
    elemRange.deleteContents();
    // parent.appendChild(this.root);
    
    const elem = document.createElement(this.type);

    for (let key in this.props) {
      // if (this.props.hasOwnProperty(key)) {
        const value = this.props[key];
        // 判断元素是否定义了事件 - 正则匹配以 on 开头的属性
        const regx = key.match(/^on([\s\S]+)$/);
        // console.log(regx1); // 等价，正则还需要多熟悉
        // const regx1 = key.match(/^on([\w\W]+)$/);
        // console.log(regx1); // 等价，正则还需要多熟悉
        // const regx2 = key.match(/^on(.+)$/);
        // console.log(regx2); // 稍有区别的等价，正则还需要多熟悉
        if (regx) {
          // 优化了老师处理时间名大小写使用的方式
          // 匹配到了即是可用于 addEventListener 监听的 事件名，只需要将其转换小写即可
          elem.addEventListener((RegExp.$1).toLowerCase(), value);

          // React 中没有实现 removeEventListener 函数，这也是 React 中的一个缺点
          // 他只是无脑的比对 vdom，而不是选择维护 vdom
        }
        // 处理 className 属性为可识别的 class 属性
        if (key === "className") { key = "class"; }
        elem.setAttribute(key, value);
      // }
    }

    for (const child of this.children) {
      // 更新为使用 document.createRange 函数
      const elemRange = document.createRange();
      if (elem.children.length) { // 如果该真实元素有子元素
        elemRange.setStartAfter(elem.lastChild);
        elemRange.setEndAfter(elem.lastChild);
      } else { // 如果该真实元素没有子元素
        elemRange.setStart(elem, 0);
        elemRange.setEnd(elem, 0);
      }
      child.mountTo(elemRange);
    }

    elemRange.insertNode(elem);
  }
}
// 文本节点封装类实现
class TextNodeWrapper {
  // 构造函数，创建一个文本节点
  constructor(content) {
    this.root = document.createTextNode(content);
  }
  // 装载函数，将创建后的元素装载到指定元素中
  mountTo(elemRange) {
    // parent.appendChild(this.root);

    elemRange.deleteContents();
    elemRange.insertNode(this.root);
  }
}

export const ToyReact = {
  /**
   * 创建元素，该函数会经过 webpack 调用 @babel/plugin-transform-react-jsx 插件
   * 将 jsx 代码翻译为 js 代码，并调用 pragma 配置，默认为调用 React.createElement
   * 其中 createElement 会指向该函数
   * @param {*} type 元素类型，小写时传入字符串，大写时传入参数
   * @param {*} attributes 元素属性
   * @param  {...any} children 元素的子元素
   */
  createElement(type, attributes, ...children) {
    // console.log(this);
    // console.log(arguments);

    // 创建一个 元素 但是对于 document.documentElement 来说是不可见也无法访问的
    // const elem = document.createElement(type);
    let elem;

    // 如果 type 是文本类型，则调用元素封装类
    if (typeof type === "string") {
      elem = new ElementWrapper(type); // 创建 元素封装类 实例
    } else { // 非文本类型则直接调用并创建该 type 实例
      elem = new type;
    }
    // console.dir(elem);

    // 循环元素的属性
    for (const attributeName in attributes) {
      // 防止意外遍历非自身属性
      if (attributes.hasOwnProperty(attributeName)) {
        // 获取属性值
        const attributeValue = attributes[attributeName];
        // 设置元素属性
        elem.setAttribute(attributeName, attributeValue);
      }
    }

    /**
     * 插入子元素，因为子元素类型可能是多种，所以需要对类型进行判断
     * @param {*} children 子元素
     */
    const insertChildren = (children) => {
      // 循环获取子元素
      for (const child of children) {
        // if (Array.isArray(child)) { // 如果子元素还是一个数组则递归调用插入子元素
        if (typeof child === "object" && child instanceof Array) { // 如果子元素还是一个数组则递归调用插入子元素
          insertChildren(child);
        } else {
          if (child === null || child === void 0) { child = ""; }
          if (!(child instanceof Component)
            // 使用 ES6 继承 Component 可以不需要判断此类型
            && !(child instanceof ElementWrapper) 
            && !(child instanceof TextNodeWrapper)) {
            child = String(child);
          }
          // 如果子元素是一个字符串
          if (typeof child === "string") {
            // 创建一个文本节点
            child = new TextNodeWrapper(child);
          }
          // 将子元素添加至创建的元素 elem
          elem.appendChild(child);
        }
      }
    }
    insertChildren(children);

    // 经过 @babel/preset-env 的翻译，会将比较先进语法翻译为比较保守的 ECMA Script 5 的语法 

    // 此时 元素 对于 document.documentElement 来还是不可见也无法访问的
    // 当使用 document.body.appendChild(elem); 时会将该 元素 添加至 document.documentElement 中
    // 此时在页面中就是可见且可访问的 元素 了，可见的前提是 style display 不为 none
    return elem;
  },

  /**
   * 将 VDom 添加至 真实的 element 上以将其渲染出来
   * @param {*} vdom 虚拟的 dom 元素
   * @param {*} element 真实的元素
   */
  render(vdom, element) {
    // element.appendChild(vdom);

    // 使用 document.createRange 函数来将元素对象渲染到页面上
    // 关于 document.createRange 函数还没有进行了解，所以这里只是照做，没有更多的注释
    const elemRange = document.createRange();
    if (element.children, length) { // 如果该真实元素有子元素
      elemRange.setStartAfter(element.lastChild);
      elemRange.setEndAfter(element.lastChild);
    } else { // 如果该真实元素没有子元素
      elemRange.setStart(element, 0);
      elemRange.setEnd(element, 0);
    }
    vdom.mountTo(elemRange);
  }
};